package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

/*
1、需求：监控goroutine的cpu信息---->可以主动退出监控程序
2、父的goroutine的向子的goroutine传递控制信息的方式:
(1): 通过channel
(2): context
3、context主要用来在goroutine之间传递上下文信息，包括：取消信号、超时时间、截止时间、传值
4、context包提供了三种函数: WithCancel、WithTimeout、WithValue
5、Go 里并没有直接为我们提供一个统一的 context 对象，而是设计了一个接口类型的 Context。然后在接口上来实现了几种具体类型的context

	type Context interface {
	    Deadline() (deadline time.Time, ok bool)
	    Done() <-chan struct{}
	    Err() error
	    Value(key any) any
	}

6、接口核心API四个方法：
Deadline() ：返回过期时间，如果ok为false，说明没有设置过期时间。不常用
Done()：返回一个channel，而且它的数据类型是 struct{}，一个空结构体，因此在Go里都是直接通过close channel来进行通知的，不会涉及具体数据传输。一般用于监听Context实例的信号，比如说过期，或者正常关闭。常用
Err()：返回一个错误用于表达Context发生了什么。如果上面的Done()的channel没被close，则error为 nil；如果channel已被close，则error将会返回 close的原因，比如超时或手动取消。Canceled=>正常关闭，DeadlineExceeded=>过期超时。比较常用。
Value()：是用来存储具体数据的方法，取值。非常常用
7、context类型
emptyCtx：表示什么都没有的 context，一般用作最初始的 context，作为父 context 使用；是一个空的 context，永远不会被 cancel，没有存储值，也没有 deadline。通过Background()和 TODO()方法创建。background 通常用在 main 函数中，作为所有 context 的根节点。todo 通常用在并不知道传递什么 context 的情形。
cancelCtx：核心是可以主动取消的上下文，它取消的时候还会将所有由该上下文派生的的子上下文一并取消，主要是通过 mu，done，children，err 实现，WithCancel 方法创建。mu：锁，用于保护并发 ，首先根据 cancelCtx 的核心需求，可以取消派生的所有上下文，也就意味着我们需要存储这个 context 派生的所有子context，那我们推测这个锁的作用应该就是保护存储子上下文或者删除子上下文的结构体。（总结会含有真正作用）done：推测用于标识此结构体是否结束。children：是一个 canceler 的 map，可以发现就是用于存储上下文的结构题。err:错误信息，用于判断是否已经取消。children 的作用：因为 cancelContext 是父子相关联的，一个 cancelContext 取消的时候既需要干掉自己所有的子，也需要告诉自己的父。Mu 的作用：context 被多个协程互相传递使用，这就要保证它一定要是并发安全的，实现过程中各种修改操作，如取消，删除子，增加子，都需要用锁保证并发安全。
timerCtx：核心是在到达指定时间后自动 cancel，所以相对于 cancelCtx，它只新增了两个结构体，WithDeadline 方法创建。timer：计时器。deadline：截止时间
valueCtx：用来传值的 context，WithValue 方法创建。valueCtx 通过 key-value形式来存储数据，当找不到 key 时，就会到父 context 里查找，直到没有父 context。和链表有点像，只是它的方向相反：Context 指向它的父节点，链表则指向下一个节点。通过 WithValue 函数，可以创建层层的 valueCtx，存储goroutine 间可以共享的变量。因为查找方向是往上走的，所以，父节点没法获取子节点存储的值，子节点却可以获取父节点的值。
只能存储一个key，val：为什么不用map？
map要求key是comparable的，而我们可能用不是comparable的key
context包的设计理念就是将 Context 设计成不可变
缺点：在一个处理过程中，有若干子函数、子协程。各种不同的地方会向context 里塞入各种不同的 k-v 对，最后在某个地方使用，不清楚这些值会不会被覆 盖，很难进行排查和项目的梳理
8、cancelCtx和timerCtx 区别：
相同点：
cancelCtx 、timerCtx(用来通知用的 context)
不同点：
cancelCtx 是手动调用 cancel 方法来触发取消通知；
timerCtxt 则通过 AfterFunc 超时时间来自动触发 cancel 方法。
参考学习地址：https://www.cnblogs.com/ltaodream/p/16934230.html
*/
var wg11 sync.WaitGroup

// 采用context.Context类型作为入参
func cpuInfo(ctx context.Context) {
	defer wg11.Done()
	for {
		select {
		// context.Context中的Done()方法是个channel，等待外部调用就发送值，这里接收值，从而达到控制的作用
		case <-ctx.Done():
			fmt.Println("退出cpu监控")
			return
		default:
			fmt.Println("获取cpu信息")
			time.Sleep(time.Second * 1)
		}
	}

}

func main() {
	wg11.Add(1)
	/*
		一、context.WithCancel实现goroutine控制，主动取消
	*/
	// context包提供了三种函数: WithCancel、WithTimeout、WithValue
	// 如过你的goroutine，函数中，如果希望被控制、超时、传值、但又不想影响原来的函数接口信息，那么第一个参数可以加上一个context
	//父的context，context.WithCancel返回一个context和一个方法，当调用这个方法的时候就是发送消息，等待Done()接收
	//context.Background()代表创建父的context
	ctx1, cancel1 := context.WithCancel(context.Background()) //emptyCtx
	//子的context，cancelCtx：核心是可以主动取消的上下文，它取消的时候还会将所有由该上下文派生的的子上下文(父子context)一并取消
	ctx2, _ := context.WithCancel(ctx1)
	// go cpuInfo(ctx2)  // 注意: 传入的是父的context
	go cpuInfo(ctx2) // 注意: 这里我们传入的是子的context，但是调用的是父的返回函数，同样达到了控制的效果，取消所有的context
	time.Sleep(time.Second * 3)
	//	//将监控函数执行一段时间之后，调用context.WithCancel()的返回函数，给Done()方法发送消息，从而去控制
	cancel1()
	//cancel2()   //这里就算调用ctx2一起返回的函数也可以取消所有派生context

	/*
		二、context.WithTimeout实现超时，自动取消
	*/
	wg11.Wait()

}
